package com.tools.web.filter.gateway;

import com.tools.common.container.RowKit;
import com.tools.common.object.Note;
import com.tools.common.thread.TimePair;
import com.tools.common.thread.Timeunit;
import com.tools.web.WebKit;
import com.tools.web.interceptor.auth.AuthenticationManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;

import java.util.Objects;
import java.util.Set;

/**
 * 使用 RedisTemplate 客户端操作 Redis 实现的网关过滤器
 * */
@Note("使用 RedisTemplate 客户端操作 Redis 实现的网关过滤器")
public final class RedisTemplateGatewayManager extends AbsGatewayManager implements GatewayRedisLuaScript {

    @Note("操作 Redis 的客户端实例")
    private final RedisTemplate<String, String> redisTemplate;

    @Note("是否禁用所有的 REST 接口")
    private boolean disabledAllRestApi = false;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisTemplateGatewayManager.class);

    /* ********************************************************************************************
     *
     *         构造器
     *
     * *********************************************************************************************
     * */

    public RedisTemplateGatewayManager(RedisTemplate<String, String> redisTemplate, GatewaySource source) {
        this(redisTemplate, source, null);
    }

    public RedisTemplateGatewayManager(RedisTemplate<String, String> redisTemplate, GatewaySource source, AuthenticationManager authenticationManager) {
        super(source, authenticationManager);
        this.redisTemplate = Objects.requireNonNull(redisTemplate);
    }

    /* ********************************************************************************************
     *
     *         实现的方法
     *
     * *********************************************************************************************
     * */

    @Override
    protected Logger protected_getLogger() {
        return LOGGER;
    }

    @Override
    protected long protected_toSubclassDomainUnitTime(TimePair addBlackerThresholdTime) {
        return Timeunit.toSecond(addBlackerThresholdTime);
    }

    @Override
    protected String protected_checkIfAddIPInBlacksAndGetTimeunitLabel(String clientIP) {
        String key = super.protected_getAddBlackSaveKey(clientIP);
        int addBlackerThreshold = super.getAddBlackerThreshold();
        long addBlackerThresholdTime = super.getAddBlackerThresholdTime();
        Integer result = this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                HAS.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                String.valueOf(addBlackerThresholdTime).getBytes(),
                String.valueOf(addBlackerThreshold).getBytes()
        ));
        if(result != null && result == 1) return null;
        super.addInIpBlacks(clientIP);
        this.redisTemplate.delete(key);
        return Timeunit.SECOND.getText();
    }

    @Override
    protected void protected_setDisabledAllURIProperty(boolean all) {
        this.disabledAllRestApi = all;
    }

    @Override
    protected int protected_initDisabledURIs(Set<String> disabledURIs) {
        String[] newURIArray = super.protected_checkAsNewURISet(disabledURIs, null);
        if(newURIArray == null) return 0;
        Long result = this.redisTemplate.opsForSet().add(super.getDisableURICacheKey(), newURIArray);
        return result != null ? result.intValue() : 0;
    }

    @Override
    protected boolean protected_isInDisabledURIs(String uri) {
        Boolean result = this.redisTemplate.opsForSet().isMember(super.getDisableURICacheKey(), uri);
        return result != null && result;
    }

    @Override
    public RedisTemplateGatewayManager addInDisabledURIs(String uri) {
        if(this.enabledDisabledURIs()) {
            if("*".equals(uri)) this.disabledAllRestApi = true;
            else this.redisTemplate.opsForSet().add(super.getDisableURICacheKey(),
                    WebKit.uriAddServerContextPath(uri, super.getServerContextPath()));
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInDisabledURIs(String... uris) {
        if(this.enabledDisabledURIs()) {
            String[] newURIs = super.protected_checkAsNewURISet(uris, true);
            if(newURIs != null) this.redisTemplate.opsForSet().add(super.getDisableURICacheKey(), newURIs);
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInDisabledURIs(Iterable<String> uris) {
        if(this.enabledDisabledURIs()) {
            String[] newURIs = super.protected_checkAsNewURISet(uris, true);
            if(newURIs == null) return this;
            this.redisTemplate.opsForSet().add(super.getDisableURICacheKey(), newURIs);
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeFromDisabledURIs(String uri) {
        if(this.enabledDisabledURIs()) {
            if("*".equals(uri)) this.disabledAllRestApi = false;
            else this.redisTemplate.opsForSet().remove(super.getDisableURICacheKey(),
                    WebKit.uriAddServerContextPath(uri, super.getServerContextPath()));
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromDisabledURIs(String... uris) {
        if(this.enabledDisabledURIs()) {
            Object[] newURIs = this.private_toObjectArray(uris);
            if(newURIs != null) this.redisTemplate.opsForSet().remove(super.getDisableURICacheKey(), newURIs);
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromDisabledURIs(Iterable<String> uris) {
        if(this.enabledDisabledURIs()) {
            Object[] newURIs = this.private_toObjectArray(uris);
            if(newURIs == null) return this;
            this.redisTemplate.opsForSet().remove(super.getDisableURICacheKey(), newURIs);
        }
        return this;
    }

    @Override
    public RedisTemplateGatewayManager clearDisabledURIs() {
        if(this.enabledDisabledURIs()) {
            this.redisTemplate.delete(super.getDisableURICacheKey());
        }
        return this;
    }

    @Override
    public boolean isInDisabledURIs(String uri) {
        if(this.enabledDisabledURIs()) {
            if(this.disabledAllRestApi) return true;
            Boolean result = this.redisTemplate.opsForSet().isMember(super.getDisableURICacheKey(),
                    WebKit.uriAddServerContextPath(uri, super.getServerContextPath()));
            return result != null && result;
        }
        return false;
    }

    @Override
    public boolean isAllInDisabledURIs(String... uris) {
        if(this.enabledDisabledURIs()) {
            if(this.disabledAllRestApi) return true;
            String[] newURIs = super.protected_checkAsNewURISet(uris, null);
            if(newURIs == null) return false;
            String disableURICacheKey = super.getDisableURICacheKey();
            SetOperations<String, String> opsForSet = this.redisTemplate.opsForSet();
            for(String uri : newURIs) {
                Boolean result = opsForSet.isMember(disableURICacheKey, uri);
                if(result == null || !result) return false;
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean isAllInDisabledURIs(Iterable<String> uris) {
        if(this.enabledDisabledURIs()) {
            if(this.disabledAllRestApi) return true;
            String[] newURIs = super.protected_checkAsNewURISet(uris, null);
            if(newURIs != null) {
                SetOperations<String, String> opsForSet = this.redisTemplate.opsForSet();
                String disableURICacheKey = super.getDisableURICacheKey();
                for(String uri : newURIs) {
                    Boolean result = opsForSet.isMember(disableURICacheKey, uri);
                    if(result == null || !result) return false;
                }
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isAnyInDisabledURIs(String... uris) {
        if(this.enabledDisabledURIs()) {
            if(this.disabledAllRestApi) return true;
            String[] newURIs = super.protected_checkAsNewURISet(uris, null);
            if(newURIs == null) return false;
            SetOperations<String, String> opsForSet = this.redisTemplate.opsForSet();
            String disableURICacheKey = super.getDisableURICacheKey();
            for(String uri : newURIs) {
                Boolean result = opsForSet.isMember(disableURICacheKey, uri);
                if(result != null && result) return true;
            }
        }
        return false;
    }

    @Override
    public boolean isAnyInDisabledURIs(Iterable<String> uris) {
        if(this.enabledDisabledURIs()) {
            if(this.disabledAllRestApi) return true;
            String[] newURIs = super.protected_checkAsNewURISet(uris, null);
            if(newURIs != null) {
                String disableURICacheKey = super.getDisableURICacheKey();
                SetOperations<String, String> opsForSet = this.redisTemplate.opsForSet();
                for(String uri : newURIs) {
                    Boolean result = opsForSet.isMember(disableURICacheKey, uri);
                    if(result != null && result) return true;
                }
            }
        }
        return false;
    }

    /* ********************************************************************************************
     *
     *         重写的方法
     *
     * *********************************************************************************************
     * */

    @Override
    public RedisTemplateGatewayManager addInProxyWhites(String remote) {
        super.addInProxyWhites(remote);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInProxyWhites(String... remotes) {
        super.addAllInProxyWhites(remotes);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInProxyWhites(Iterable<String> remotes) {
        super.addAllInProxyWhites(remotes);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeFromProxyWhites(String remote) {
        super.removeFromProxyWhites(remote);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromProxyWhites(String... remotes) {
        super.removeAllFromProxyWhites(remotes);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromProxyWhites(Iterable<String> remotes) {
        super.removeAllFromProxyWhites(remotes);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager clearProxyWhites() {
        super.clearProxyWhites();
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addInIpWhites(String clientIP) {
        super.addInIpWhites(clientIP);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInIpWhites(String... clientIPs) {
        super.addAllInIpWhites(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addAllInIpWhites(Iterable<String> clientIPs) {
        super.addAllInIpWhites(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeFromIpWhites(String clientIP) {
        super.removeFromIpWhites(clientIP);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromIpWhites(String... clientIPs) {
        super.removeAllFromIpWhites(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromIpWhites(Iterable<String> clientIPs) {
        super.removeAllFromIpWhites(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager clearIpWhites() {
        super.clearIpWhites();
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addInIpBlacks(String clientIP) {
        super.addInIpBlacks(clientIP);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addInIpBlacks(String... clientIPs) {
        super.addInIpBlacks(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager addInIpBlacks(Iterable<String> clientIPs) {
        super.addInIpBlacks(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeFromIpBlacks(String clientIP) {
        super.removeFromIpBlacks(clientIP);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromIpBlacks(String... clientIPs) {
        super.removeAllFromIpBlacks(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager removeAllFromIpBlacks(Iterable<String> clientIPs) {
        super.removeAllFromIpBlacks(clientIPs);
        return this;
    }

    @Override
    public RedisTemplateGatewayManager clearIpBlacks() {
        super.clearIpBlacks();
        return this;
    }


    /* ********************************************************************************************
     *
     *         私有逻辑
     *
     * *********************************************************************************************
     * */

    private Object[] private_toObjectArray(String[] source) {
        if(source == null) return null;
        int length = source.length;
        if(length == 0) return null;
        Object[] newArray = new Object[length];
        String contextPath = super.getServerContextPath();
        for (int i = 0; i < length; i++) {
            String uri = source[i];
            if("*".equals(uri)) this.disabledAllRestApi = false;
            newArray[i] = WebKit.uriAddServerContextPath(uri, contextPath);
        }
        return newArray;
    }

    private Object[] private_toObjectArray(Iterable<String> source) {
        int size = RowKit.getIterableSize(source);
        if(size == 0) return null;
        Object[] newArray = new Object[size];
        int index = 0;
        String contextPath = super.getServerContextPath();
        for (String uri : source) {
            if("*".equals(uri)) this.disabledAllRestApi = false;
            newArray[index] = WebKit.uriAddServerContextPath(uri, contextPath);
        }
        return newArray;
    }
}
